﻿//===================================================================================
// Copyright (c) Ryan Garrett.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===================================================================================

//FUTURE: Remember to create snippets. Creating a snippet to ease creating computation methods would be nice
//WRITE: When merging model graphs, computed property values after the merge are not determinate. Whether or not they merge correctly depends on whether or not
//       the computed property is registered after the properties it is derived from and whether or not it has an explicit value set. In order to make sure
//       merging computed properties works consistently, the developer has to make sure that the computed property is always registered last. If using the snippets
//       to create properties, the computed properties will generally be registered last because they have to be registered in the constructor.
using System;
using System.Collections.Generic;

namespace Caffeine.Core.ModelProperty
{
    internal sealed class ComputedModelProperty<RegisteredPropertyType, PropertyType> : ModelProperty<PropertyType>
    {
        #region State

        private readonly Func<IEnumerable<ModelProperty<RegisteredPropertyType>>, RegisteredPropertyType, PropertyType> _computation;
        private readonly IEnumerable<ModelProperty<RegisteredPropertyType>> _computedProperties;

        #endregion  

        #region Constructors

        public ComputedModelProperty
        (
            ModelBase parent,
            String name,
            Nullable<ModelPropertyBehaviors> behaviors,
            Boolean isWrapped,
            Func<IEnumerable<ModelProperty<RegisteredPropertyType>>, RegisteredPropertyType, PropertyType> computation,
            params ModelProperty<RegisteredPropertyType>[] computedProperties
        )   : base(parent, name, behaviors, isWrapped)
        {
            _computation = computation;
            _computedProperties = computedProperties;
            
            foreach (ModelProperty<RegisteredPropertyType> property in computedProperties)
            {
                property.PropertySync = new ComputedModelPropertySync<RegisteredPropertyType, PropertyType>(this);
                property.ForceSync();
            }
        }

        #endregion

        #region Public

        public IEnumerable<ModelProperty<RegisteredPropertyType>> ComputedProperties
        {
            get
            {
                return _computedProperties;
            }
        }

        public Func<IEnumerable<ModelProperty<RegisteredPropertyType>>, RegisteredPropertyType, PropertyType> Computation
        {
            get
            {
                return _computation;
            }
        }

        public override Boolean IsComputed
        {
            get
            {
                return true;
            }
        }

        public override String ToString()
        {
            String stringValue = null;

            if (Value != null)
            {
                stringValue = Value.ToString();
            }

            return stringValue;
        }

        #endregion

        #region Internal

        internal void SyncValue(RegisteredPropertyType value)
        {
            Value = Computation(ComputedProperties, value);
        }

        internal override void MergeAdjacentProperty(ModelPropertyBase property)
        {
            Value = (property as ComputedModelProperty<RegisteredPropertyType, PropertyType>).Value;
        }

        #endregion

        #region Protected

        protected override Boolean OnCompareValues(PropertyType currentValue, PropertyType newValue)
        {
            Boolean areEqual = false;

            if (currentValue != null && newValue != null)
            {
                areEqual = currentValue.Equals(newValue);
            }
            else
            {
                areEqual = currentValue == null && newValue == null;
            }

            return areEqual;
        }

        #endregion
    }
}
